﻿Imports System
Imports System.Text
Imports System.Data
Imports System.Collections.Generic
Imports System.Web
Imports System.IO

Imports Framework

'Collection Class (Customisable half)
Partial Public Class CProductList
        
#Region "Filters"
    Public ReadOnly Property Active As CProductList
        Get
            Return GetByIsActive(True)
        End Get
    End Property
    Public Function HavingOrders() As CProductList
        Dim list As New CProductList(Me.Count)
        For Each i As CProduct In Me
            If i.OrdersCountCached > 0 Then list.Add(i)
        Next
        Return list
    End Function
    Public Function HavingSales() As CProductList
        Dim list As New CProductList(Me.Count)
        For Each i As CProduct In Me
            If i.PaymentsCountCached > 0 Then list.Add(i)
        Next
        Return list
    End Function

    Private m_havingTranscripts As CProductList
    Public Function HavingTranscripts() As CProductList
        If IsNothing(m_havingTranscripts) Then
            Dim list As New CProductList(Me.Count)
            For Each i As CProduct In Me
                If i.HasTranscript Then list.Add(i)
            Next
            list.Sort()
            m_havingTranscripts = list
        End If
        Return m_havingTranscripts
    End Function

    Private m_havingChildrenWithTranscripts As CProductList
    Public Function HavingChildrenWithTranscripts() As CProductList
        If IsNothing(m_havingChildrenWithTranscripts) Then
            Dim list As New CProductList(Me.Count)
            For Each i As CProduct In Me
                If i.Children.HavingTranscripts.Count = 0 Then Continue For
                list.Add(i)
            Next
            list.Sort()
            m_havingChildrenWithTranscripts = list
        End If
        Return m_havingChildrenWithTranscripts
    End Function



    Private m_havingTranscriptsChineseSimp As CProductList
    Public Function HavingTranscriptsChineseSimp() As CProductList
        If IsNothing(m_havingTranscriptsChineseSimp) Then
            Dim list As New CProductList()
            For Each i As CProduct In Me
                If i.HasTranscriptChineseSimp Then list.Add(i)
            Next
            list.Sort()
            m_havingTranscriptsChineseSimp = list
        End If
        Return m_havingTranscriptsChineseSimp
    End Function

    Private m_havingTranscriptsChineseTrad As CProductList
    Public Function HavingTranscriptsChineseTrad() As CProductList
        If IsNothing(m_havingTranscriptsChineseTrad) Then
            Dim list As New CProductList()
            For Each i As CProduct In Me
                If i.HasTranscriptChineseTrad Then list.Add(i)
            Next
            list.Sort()
            m_havingTranscriptsChineseTrad = list
        End If
        Return m_havingTranscriptsChineseTrad
    End Function

    Private m_havingVideos As CProductList
    Public Function HavingVideos() As CProductList
        If IsNothing(m_havingVideos) Then
            Dim list As New CProductList()
            For Each i As CProduct In Me
                If i.HasVideos Then list.Add(i)
            Next
            m_havingVideos = list
        End If
        Return m_havingVideos
    End Function

    Private m_havingAudio As CProductList
    Public Function HavingMp3() As CProductList
        If IsNothing(m_havingAudio) Then
            Dim list As New CProductList()
            For Each i As CProduct In Me
                If i.Files.HasMp3 Then list.Add(i)
            Next
            m_havingAudio = list
        End If
        Return m_havingAudio
    End Function

    Public Function HavingNotes() As CProductList
        Dim list As New CProductList()
        For Each i As CProduct In Me
            If i.HasNotes Then list.Add(i)
        Next
        Return list
    End Function
    Public Function HavingOnVimeo() As CProductList
        Dim list As New CProductList()
        For Each i As CProduct In Me
            If i.HasVideosOnVimeo Then list.Add(i)
        Next
        Return list
    End Function
    Public Function HavingOnYouTube() As CProductList
        Dim list As New CProductList()
        For Each i As CProduct In Me
            If i.HasVideosOnYouTube Then list.Add(i)
        Next
        Return list
    End Function

    Public Function HavingMobi() As CProductList
        Dim list As New CProductList()
        For Each i As CProduct In Me
            If i.Files.HasMobi Then list.Add(i)
        Next
        Return list
    End Function
    Public Function HavingEpub() As CProductList
        Dim list As New CProductList()
        For Each i As CProduct In Me
            If i.Files.HasEpub Then list.Add(i)
        Next
        Return list
    End Function


    Public ReadOnly Property ConferenceSets As CProductList
        Get
            Return GetByTypeId(EProductType.ConferenceSet)
        End Get
    End Property
    Public ReadOnly Property Manuals As CProductList
        Get
            Return GetByTypeId(EProductType.Manual)
        End Get
    End Property
    Public ReadOnly Property Documents As CProductList
        Get
            Return GetByTypeId(EProductType.Document)
        End Get
    End Property
    Public ReadOnly Property Sermons As CProductList
        Get
            Return GetByTypeId(EProductType.Sermon)
        End Get
    End Property
    Public ReadOnly Property Paperbacks As CProductList
        Get
            Return GetByTypeId(EProductType.Paperback)
        End Get
    End Property

    Public ReadOnly Property StandAlone As CProductList
        Get
            Return GetByBelongsToProductId(Integer.MinValue)
        End Get
    End Property
    Public ReadOnly Property MikeConnell As CProductList
        Get
            Return GetBySpeakerId(ESpeaker.MikeConnell)
        End Get
    End Property

    Public ReadOnly Property Art As CProductList
        Get
            Return GetByImageScribeId(eScribe.Colleen)
        End Get
    End Property


    Public ReadOnly Property Chinese As CProductList
        Get
            Return GetByLanguageId(ELanguage.Chinese)
        End Get
    End Property
#End Region

#Region "Aggregation"
    Public ReadOnly Property BelongsTo As CProductList
        Get
            Dim list As New CProductList
            For Each i As CProduct In Me
                If Not IsNothing(i.BelongsTo) Then
                    If Not list.Contains(i.BelongsTo) Then list.Add(i.BelongsTo)
                End If
            Next
            list.Sort()
            Return list
        End Get
    End Property
    Public ReadOnly Property ByConferenceSet As CProductList
        Get
            Dim list As New CProductList
            list.Add(Me.StandAlone)
            For Each i As CProduct In BelongsTo
                list.Add(Me.GetByBelongsToProductId(i.ProductId))
            Next
            Return list
        End Get
    End Property

    Public ReadOnly Property NamesAndPrices As String
        Get
            Dim sb As New StringBuilder
            For Each i As CProduct In Me
                sb.AppendLine(i.ProductNameAndPrice)
            Next
            Return sb.ToString
        End Get
    End Property
    Public ReadOnly Property NameAndContents As String
        Get
            Dim sb As New StringBuilder
            For Each i As CProduct In Me
                sb.AppendLine(i.ProductNameAndContents)
            Next
            Return sb.ToString
        End Get
    End Property

    Public ReadOnly Property Total As Decimal
        Get
            Dim d As Decimal = 0
            For Each i As CProduct In Me
                d += i.ProductPrice
            Next
            Return d
        End Get
    End Property

    Public ReadOnly Property Speakers As CSpeakerList
        Get
            Dim list As New CSpeakerList
            For Each i As CProduct In Me
                If Not list.Contains(i.Speaker) Then list.Add(i.Speaker)
                If Not IsNothing(i.Speaker2) Then
                    If Not list.Contains(i.Speaker2) Then list.Add(i.Speaker2)
                End If
            Next
            Return list
        End Get
    End Property

    Public ReadOnly Property Languages As CLanguageList
        Get
            Dim list As New CLanguageList
            For Each i As CProduct In Me
                If Not list.Contains(i.Language) Then list.Add(i.Language)
            Next
            Return list
        End Get
    End Property
#End Region

#Region "Searching (Optional)"
    'Represents a simple search box to search PK and any string columns (add overloads as required, based on the pattern below)
    'e.g. Public Function Search(ByVal nameOrId As String, ByVal typeId As Byte, ByVal paypalButtonId As Integer, ByVal isActive As Boolean?, ByVal isFree As Boolean?) As CProductList
    Public Function Search(typeId As Byte?, speakerId As Byte?, scribeId As Byte?, languageId As Byte?, locationId As Byte?, buttonId As Integer, ByVal nameOrId As String, isActive As Boolean?) As CProductList
        '1. Normalisation
        If IsNothing(nameOrId) Then nameOrId = String.Empty
        nameOrId = nameOrId.Trim.ToLower()

        '2. Start with a complete list
        Dim results As CProductList = Me

        '3. Use any available index, such as those generated for fk/bool columns
        If typeId.HasValue AndAlso typeId.Value < 100 AndAlso typeId > 0 Then results = results.GetByTypeId(typeId.Value)
        If speakerId.HasValue Then results = results.GetBySpeaker1or2Id(speakerId.Value)
        If scribeId.HasValue Then results = results.GetByScribe1or2Id(scribeId.Value)
        If languageId.HasValue Then results = results.GetByLanguageId(languageId.Value)
        If locationId.HasValue Then results = results.GetByLocationId(locationId.Value)
        If buttonId > 0 Then results = results.GetByPaypalButtonId(buttonId)
        If isActive.HasValue Then results = results.GetByIsActive(isActive.Value) 'Customise bool filters according to UI (e.g. for checkbox, use simple bool and bias in one direction)

        If typeId = 100 Then results = results.HavingOrders
        If typeId = 200 Then results = results.HavingSales

        'Special case - unique index (e.g. primary key)
        If Not String.IsNullOrEmpty(nameOrId) Then
            Dim id As Integer
            If Integer.TryParse(nameOrId, id) Then
                Dim temp As New CProductList(10)

                'Match productId, smashwordsId, createspaceId
                Dim obj As CProduct = Me.GetById(id)
                If Not IsNothing(obj) Then temp.Add(obj)
                temp.AddRange(results.GetBySmashwordsId(id))
                temp.AddRange(results.GetByCSBookIds(id))
                If temp.Count > 0 Then Return temp
            End If
        End If


        '4. Exit early if remaining (non-index) filters are blank
        Return results.Search(nameOrId)
    End Function

    Private m_dict As Dictionary(Of String, CProductList)
    Public Function Search(name As String) As CProductList
        If String.IsNullOrEmpty(name) Then Return Me

        name = name.ToLower.Trim
        If IsNothing(m_dict) Then m_dict = New Dictionary(Of String, CProductList)

        Dim list As CProductList = Nothing
        If m_dict.TryGetValue(name, list) Then Return list

        Dim temp As CProductList = Me.SortByCreatedDescName

        list = New CProductList
        For Each i As CProduct In temp
            If MatchName(name, i) Then list.Add(i)
        Next

        For Each i As CProduct In temp
            If MatchDescOrKey(name, i) AndAlso Not list.Contains(i) Then list.Add(i)
        Next
        For Each i As CProduct In temp
            If MatchOther(name, i) AndAlso Not list.Contains(i) Then list.Add(i)
        Next

        m_dict(name) = list
        Return list
    End Function
    'Manual Searching e.g for string-based columns i.e. anything not indexed (add more params if required)
    Private Function MatchName(ByVal name As String, ByVal obj As CProduct) As Boolean
        If obj.ProductName.ToLower().Contains(name) Then Return True
        If obj.SpeakerNames.ToLower().Contains(name) Then Return True
        If Not IsNothing(obj.ProductNameChineseSimplified) AndAlso obj.ProductNameChineseSimplified.Contains(name) Then Return True

        'exact match on idsx3
        Dim id As Integer
        If Integer.TryParse(name, id) AndAlso id > 0 Then
            'Match productId, smashwordsId, createspaceId
            With obj
                If .ProductId = id Then Return True
                If .ProductSmashwordsIdOrParent = id Then Return True
                If .ProductVimeoId = id Then Return True
                If .ProductCSBookId = id Then Return True
                If .ProductCSBook2Id = id Then Return True
                If .ProductCSBook3Id = id Then Return True
                If .HasParent Then
                    With obj.BelongsTo
                        If .ProductCSBookId = id Then Return True
                        If .ProductCSBook2Id = id Then Return True
                        If .ProductCSBook3Id = id Then Return True
                    End With
                End If
            End With
        End If

        If Not String.IsNullOrEmpty(obj.ProductAmazonId) AndAlso obj.ProductAmazonId.ToLower().Equals(name) Then Return True
        If Not String.IsNullOrEmpty(obj.ProductVimeoChannelId) AndAlso obj.ProductVimeoChannelId.ToString.Equals(name) Then Return True
        If Not String.IsNullOrEmpty(obj.ProductYouTubeId) AndAlso obj.ProductYouTubeId.Equals(name) Then Return True
        If Not String.IsNullOrEmpty(obj.ProductYouTubePlaylistId) AndAlso obj.ProductYouTubePlaylistId.Equals(name) Then Return True
        Return False
    End Function
    Private Function MatchDescOrKey(ByVal name As String, ByVal obj As CProduct) As Boolean
        If obj.ProductDescription.ToLower().Contains(name) Then Return True
        If obj.ProductKeywords.ToLower().Contains(name) Then Return True
        If Not IsNothing(obj.ProductKeywordsChineseSimplified) AndAlso obj.ProductKeywordsChineseSimplified.Contains(name) Then Return True
        If Not IsNothing(obj.ProductDescriptionChineseSimplified) AndAlso obj.ProductDescriptionChineseSimplified.Contains(name) Then Return True

        'Parent/Child names
        If Not IsNothing(obj.BelongsTo) AndAlso MatchName(name, obj.BelongsTo) Then Return True
        For Each i As CProduct In obj.Children
            If MatchName(name, i) Then Return True
        Next

        Return False
    End Function
    Private Function MatchOther(ByVal name As String, ByVal obj As CProduct) As Boolean
        If Not String.IsNullOrEmpty(obj.ProductNotesAsPlainText) AndAlso obj.ProductNotesAsPlainText.ToLower.Contains(name) Then Return True
        If Not String.IsNullOrEmpty(obj.ProductTranscription) AndAlso obj.ProductTranscriptionLower.Contains(name) Then Return True

        'Parent/Child descriptions
        If Not IsNothing(obj.BelongsTo) AndAlso MatchDescOrKey(name, obj.BelongsTo) Then Return True
        For Each i As CProduct In obj.Children
            If MatchDescOrKey(name, i) Then Return True
        Next

        Return False
    End Function
#End Region

#Region "Cloning"
    Public Function Clone(ByVal target As CDataSrc) As CProductList ', parentId As Integer) As CProductList
        'No Transaction
        If TypeOf target Is CDataSrcRemote Then Return Clone(target, Nothing) ', parentId)

        'Transaction
        Dim cn As IDbConnection = target.Local.Connection()
        Dim tx As IDbTransaction = cn.BeginTransaction()
        Try
            Clone = Clone(target, tx) ', parentId)
            tx.Commit()
        Catch
            tx.Rollback()
            Throw
        Finally
            cn.Close()
        End Try
    End Function
    Public Function Clone(target As CDataSrc, txOrNull As IDbTransaction) As CProductList ', parentId As Integer) As CProductList
        Dim list As New CProductList(Me.Count)
        For Each i As CProduct In Me
            list.Add(i.Clone(target, txOrNull))  ', parentId))    *Child entities must reference the new parent
        Next
        Return list
    End Function
#End Region

#Region "Export to Csv"
    'Web - Need to add a project reference to System.Web, or comment out these two methods
    Public Sub ExportToCsv(ByVal response As HttpResponse)
        ExportToCsv(response, "Products.csv")
    End Sub
    Public Sub ExportToCsv(ByVal response As HttpResponse, ByVal fileName As String)
        CDataSrc.ExportToCsv(response, fileName) 'Standard response headers
        Dim sw As New StreamWriter(response.OutputStream)
        ExportToCsv(sw)
        sw.Flush()
        response.End()
    End Sub

    'Non-web
    Public Sub ExportToCsv(ByVal filePath As String)
        Dim sw As New StreamWriter(filePath)
        ExportToCsv(sw)
        sw.Close()
    End Sub

    'Logic
    Protected Sub ExportToCsv(ByVal sw As StreamWriter)
        Dim headings As String() = New String() {"ProductId", "ProductTypeId", "ProductName", "ProductDescription", "ProductPrice", "ProductPaypalButtonId", "ProductIsActive", "ProductCreated"}
        CDataSrc.ExportToCsv(headings, sw)
        For Each i As CProduct In Me
            Dim data As Object() = New Object() {i.ProductId, i.ProductTypeId, i.ProductName, i.ProductDescription, i.ProductPrice, i.ProductPaypalButtonId, i.ProductIsActive, i.ProductCreated}
            CDataSrc.ExportToCsv(data, sw)
        Next
    End Sub
#End Region

#Region "Alternative Sort Order (In-Memory Sort)"
    Dim m_ordered As CProductList
    Public ReadOnly Property Ordered As CProductList
        Get
            If IsNothing(m_ordered) Then
                Dim list As New CProductList(Me.Count)
                list.AddRange(Me.Manuals)
                list.AddRange(Me.Sermons)
                list.AddRange(Me.Documents)
                m_ordered = list
            End If
            Return m_ordered
        End Get
    End Property


    'Public
    Private m_createdDesc As CProductList
    Public Function SortByCreatedDescName() As CProductList
        If IsNothing(m_createdDesc) Then
            Dim copy As New CProductList(Me)
            copy.Sort(New CProduct_SortByCreatedDescName)
            m_createdDesc = copy
        End If
        Return m_createdDesc
    End Function

    Private m_created As CProductList
    Public Function SortByCreatedName() As CProductList
        If IsNothing(m_created) Then
            Dim copy As New CProductList(Me)
            copy.Sort(New CProduct_SortByCreatedName)
            m_created = copy
        End If
        Return m_created
    End Function

    'Private
    Private Class CProduct_SortByCreatedDescName : Implements IComparer(Of CProduct)
        Public Function Compare(ByVal x As CProduct, ByVal y As CProduct) As Integer Implements System.Collections.Generic.IComparer(Of CProduct).Compare
            Dim i As Integer = x.ProductCreated.CompareTo(y.ProductCreated) * -1
            If 0 <> i Then Return i
            Return x.ProductName.CompareTo(y.ProductName)
        End Function
    End Class
    Private Class CProduct_SortByCreatedName : Implements IComparer(Of CProduct)
        Public Function Compare(ByVal x As CProduct, ByVal y As CProduct) As Integer Implements System.Collections.Generic.IComparer(Of CProduct).Compare
            Dim i As Integer = x.ProductCreated.CompareTo(y.ProductCreated)
            If 0 <> i Then Return i
            Return x.ProductName.CompareTo(y.ProductName)
        End Function
    End Class
#End Region

#Region "Custom Index"
    'Index by ProductSpeakerId
    Public Function GetBySpeaker1or2Id(ByVal speakerId As Byte) As CProductList
        Dim temp As CProductList = Nothing
        If Not IndexBySpeaker1or2Id.TryGetValue(speakerId, temp) Then
            temp = New CProductList()
            IndexBySpeaker1or2Id(speakerId) = temp
        End If
        Return temp
    End Function
    <NonSerialized()> _
    Private _indexBySpeaker1or2Id As Dictionary(Of Byte, CProductList)
    Private ReadOnly Property IndexBySpeaker1or2Id() As Dictionary(Of Byte, CProductList)
        Get
            If IsNothing(_indexBySpeaker1or2Id) Then
                'Instantiate
                Dim index As New Dictionary(Of Byte, CProductList)()

                'Populate
                Dim temp As CProductList = Nothing
                For Each i As CProduct In Me
                    If Not index.TryGetValue(i.ProductSpeakerId, temp) Then
                        temp = New CProductList()
                        index(i.ProductSpeakerId) = temp
                    End If
                    temp.Add(i)

                    If i.ProductSpeaker2Id > 0 AndAlso i.ProductSpeakerId <> i.ProductSpeaker2Id Then
                        'Same product can also appear under another speaker
                        If Not index.TryGetValue(i.ProductSpeaker2Id, temp) Then
                            temp = New CProductList()
                            index(i.ProductSpeaker2Id) = temp
                        End If
                        temp.Add(i)
                    End If
                Next

                'Store
                _indexBySpeaker1or2Id = index
            End If
            Return _indexBySpeaker1or2Id
        End Get
    End Property



    'Index by ProductScribeId
    Public Function GetByScribe1or2Id(ByVal scribe1or2Id As Byte) As CProductList
        Dim temp As CProductList = Nothing
        If Not IndexByScribe1or2Id.TryGetValue(scribe1or2Id, temp) Then
            temp = New CProductList()
            IndexByScribe1or2Id(scribe1or2Id) = temp
        End If
        Return temp
    End Function
    <NonSerialized()> _
    Private _indexByScribe1or2Id As Dictionary(Of Byte, CProductList)
    Private ReadOnly Property IndexByScribe1or2Id() As Dictionary(Of Byte, CProductList)
        Get
            If IsNothing(_indexByScribe1or2Id) Then
                'Instantiate
                Dim index As New Dictionary(Of Byte, CProductList)()

                'Populate
                Dim temp As CProductList = Nothing
                For Each i As CProduct In Me
                    If Not index.TryGetValue(i.ProductScribeId, temp) Then
                        temp = New CProductList()
                        index(i.ProductScribeId) = temp
                    End If
                    temp.Add(i)


                    If Not index.TryGetValue(i.ProductImageScribeId, temp) Then
                        temp = New CProductList()
                        index(i.ProductImageScribeId) = temp
                    End If
                    If Not temp.Contains(i) Then temp.Add(i)
                Next

                'Store
                _indexByScribe1or2Id = index
            End If
            Return _indexByScribe1or2Id
        End Get
    End Property
#End Region

    Public Sub MoveTo(ByVal s As CProduct, newIndex As Integer)
        Move(s, newIndex - Me.IndexOf(s))
    End Sub

    Private m_tags As List(Of CTagFreq)
    Public ReadOnly Property Tags As List(Of CTagFreq)
        Get
            If IsNothing(m_tags) Then
                SyncLock (Me)
                    If IsNothing(m_tags) Then
                        'Build dictionary
                        Dim freqs As New Dictionary(Of String, Integer)
                        For Each i As CProduct In Me
                            If String.IsNullOrEmpty(i.ProductKeywords) Then Continue For

                            Dim keywords As List(Of String) = CUtilities.StringToListStr(i.ProductKeywords)
                            For Each j As String In keywords
                                j = j.ToLower.Trim
                                If j.Length = 0 Then Continue For

                                If freqs.ContainsKey(j) Then
                                    freqs(j) += 1
                                Else
                                    freqs(j) = 1
                                End If
                            Next
                        Next

                        m_tags = New List(Of CTagFreq)(freqs.Keys.Count)
                        For Each i As String In freqs.Keys
                            m_tags.Add(New CTagFreq(i, freqs(i)))
                        Next
                        m_tags.Sort(New CSortByFreq())
                    End If

                End SyncLock
            End If
            Return m_tags
        End Get
    End Property


    Private m_altTags As List(Of CTagFreq)
    Public ReadOnly Property AltTags As List(Of CTagFreq)
        Get
            If IsNothing(m_altTags) Then
                SyncLock (Me)
                    If IsNothing(m_altTags) Then
                        'Build dictionary
                        Dim freqs As New Dictionary(Of String, Integer)
                        For Each i As CProduct In Me
                            If String.IsNullOrEmpty(i.ProductKeywordsChineseSimplified) Then Continue For

                            Dim keywords As List(Of String) = CUtilities.StringToListStr(i.ProductKeywordsChineseSimplified.Replace("，", ","))
                            For Each j As String In keywords
                                j = j.ToLower.Trim
                                If j.Length = 0 Then Continue For

                                If freqs.ContainsKey(j) Then
                                    freqs(j) += 1
                                Else
                                    freqs(j) = 1
                                End If
                            Next
                        Next

                        m_altTags = New List(Of CTagFreq)(freqs.Keys.Count)
                        For Each i As String In freqs.Keys
                            m_altTags.Add(New CTagFreq(i, freqs(i)))
                        Next
                        m_altTags.Sort(New CSortByFreq())
                    End If

                End SyncLock
            End If
            Return m_altTags
        End Get
    End Property


    Public Class CTagFreq
        Public Tag As String
        Public Freq As Integer
        Public Sub New(s As String, i As Integer)
            Tag = s
            Freq = i
        End Sub
        Public ReadOnly Property NameAndCount As String
            Get
                Return CUtilities.NameAndCount(Tag, Freq)
            End Get
        End Property
    End Class
    Private Class CSortByFreq : Implements IComparer(Of CTagFreq)
        Public Function Compare(x As CTagFreq, y As CTagFreq) As Integer Implements System.Collections.Generic.IComparer(Of CTagFreq).Compare
            Compare = y.Freq.CompareTo(x.Freq) 'freq desc
            If Compare = 0 Then Compare = x.Tag.CompareTo(y.Tag)
        End Function
    End Class


End Class
